package cn.zz.veggie.application.service;

import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import cn.zz.comm.core.types.query.PageQuery;
import cn.zz.comm.helper.BizCommHelper;
import cn.zz.comm.types.result.P;
import cn.zz.comm.util.CollectorUtil;
import cn.zz.comm.util.DateUtil;
import cn.zz.veggie.application.assembler.PurchaseAssembler;
import cn.zz.veggie.application.packer.PurchasePacker;
import cn.zz.veggie.model.domain.Order;
import cn.zz.veggie.model.domain.OrderInfo;
import cn.zz.veggie.model.domain.Product;
import cn.zz.veggie.model.domain.Purchase;
import cn.zz.veggie.model.domain.Shop;
import cn.zz.veggie.model.query.OrderQuery;
import cn.zz.veggie.model.query.PurchaseQuery;
import cn.zz.veggie.persistence.repository.OrderInfoRepository;
import cn.zz.veggie.persistence.repository.OrderRepository;
import cn.zz.veggie.persistence.repository.ProductRepository;
import cn.zz.veggie.persistence.repository.PurchaseRepository;
import cn.zz.veggie.persistence.repository.ShopRepository;
import cn.zz.veggie.web.types.dto.mgmt.purchase.MgmtPurchaseCreateDto;
import cn.zz.veggie.web.types.dto.mgmt.purchase.MgmtPurchaseQueryDto;
import cn.zz.veggie.web.types.vo.mgmt.purchase.MgmtPurchaseDetailVo;
import cn.zz.veggie.web.types.vo.mgmt.purchase.MgmtPurchaseDetailVo.MgmtPurchaseDetailItemVo;
import cn.zz.veggie.web.types.vo.mgmt.purchase.MgmtPurchaseVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * @author zhongq
 * @since 2024/6/8
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PurchaseAppService {
	private final OrderRepository orderRepository;
	private final OrderInfoRepository orderItemRepository;
	private final PurchaseRepository purchaseRepository;
	private final ShopRepository shopRepository;
	private final ProductRepository productRepository;

	public P<MgmtPurchaseVo> adminGetList(MgmtPurchaseQueryDto dto, PageQuery pageQuery) {
		PurchaseQuery query = new PurchaseQuery();
		query.setStatTimeStart(dto.getStatTimeStart());
		query.setStatTimeEnd(dto.getStatTimeEnd());
		List<Purchase> purchases = purchaseRepository.findByQuery(query, pageQuery);
		return P.of(pageQuery, CollectorUtil.bwl(purchases, PurchasePacker::toMgmtPurchaseVo));
	}

	public void adminRefresh(Integer id) {
		Purchase purchase = purchaseRepository.findById(id);
		BizCommHelper.requireNoNull(purchase, "purchase");
		final Long statTime = purchase.getStatTime();
		savePurchaseStat(statTime, true);
	}

	@Transactional
	public void timerPurchaseStatistics() {
		final ZonedDateTime time = DateUtil.now().plusHours(4);
		final long statTime = time.withMinute((time.getHour() / 12) * 12).withMinute(0).withSecond(0).toEpochSecond();
		savePurchaseStat(statTime, false);
	}

	public List<MgmtPurchaseDetailVo> exportPurchaseDetail(Integer id) {
		Purchase purchase = purchaseRepository.findById(id);
		BizCommHelper.requireNoNull(purchase, "purchase");
		// find order detail
		OrderQuery query = OrderQuery.ofDelivery(purchase.getStatTime(), purchase.calcStatTimeEnd());
		List<Order> orders = orderRepository.findWithPage(query, PageQuery.all());
		Map<Integer, Order> orderMap = CollectorUtil.btmByKeyFun(orders, Order::getId);
		List<OrderInfo> orderItems = orderItemRepository.findByOrderIds(orderMap.keySet());
		Set<Integer> productIds = CollectorUtil.bws(orderItems, OrderInfo::getProductId);
		Map<Integer, Product> productMap = productRepository.findByIds(productIds);
		Map<Integer, Shop> shopMap = shopRepository.findByIds(CollectorUtil.bws(orders, Order::getShopId));

		Function<List<OrderInfo>, List<MgmtPurchaseDetailItemVo>> itemToPeFun = ois -> ois.stream().sorted((i1, i2) -> {
			Order o1 = orderMap.get(i1.getOrderId());
			Order o2 = orderMap.get(i2.getOrderId());
			return Objects.equals(o1.getShopId(), o2.getShopId()) ? o1.getId() - o2.getId()
					: o1.getShopId() - o2.getShopId();
		}).map(oi -> {
			Order order = orderMap.get(oi.getOrderId());
			Shop shop = shopMap.get(order.getShopId());
			return PurchasePacker.toMgmtPurchaseDetailItemVo(oi, shop);
		}).toList();

		return orderItems.stream().collect(Collectors.groupingBy(OrderInfo::getProductId)).entrySet().stream()
				.map(entry -> {
					Integer productId = entry.getKey();
					List<OrderInfo> items = entry.getValue();
					Product product = productMap.get(productId);
					// items
					List<MgmtPurchaseDetailItemVo> itemVos = itemToPeFun.apply(items);
					return PurchasePacker.toMgmtPurchaseDetailVo(product, itemVos);
				}).toList();
	}

	public void mgmtCreatePurchase(MgmtPurchaseCreateDto dto) {
		final Long statTime = dto.getStatTime();
		savePurchaseStat(statTime, false);
	}

	public boolean savePurchaseStat(Long statTime, boolean force) {
		Purchase purchase = purchaseRepository.findOne(PurchaseQuery.of(statTime));
		if (purchase != null && !force) {
			log.warn("purchase is exist not force {} , record {}", statTime, purchase.getId());
			return false;
		}
		if (purchase == null) {
			purchase = Purchase.ofStatTime(statTime);
		}
		final long statTimeStart = purchase.getStatTime();
		final long statTimeEnd = purchase.calcStatTimeEnd();
		OrderQuery query = OrderQuery.ofDelivery(statTimeStart, statTimeEnd);
		List<Order> orders = orderRepository.findWithPage(query, PageQuery.all());
		List<Integer> orderIds = CollectorUtil.bwl(orders, Order::getId);
		List<OrderInfo> orderItems = orderItemRepository.findByOrderIds(orderIds);
		Map<Integer, List<OrderInfo>> productOrderItemMap = orderItems.stream()
				.collect(Collectors.groupingBy(OrderInfo::getProductId));
		PurchaseAssembler.updatePurchase(purchase, orders.size(), productOrderItemMap.size());
		purchaseRepository.save(purchase);
		return true;
	}

	public void mgmtRemovePurchase(Integer id) {
		purchaseRepository.deleteById(id);
	}
}